package br.usp.pcs.basys2.memory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import br.usp.pcs.basys2.BasysProcess;

public class MemoryManager {

	private Memory memory;
	private Queue<BasysProcess> basysProcessQueue = new LinkedList<BasysProcess>();
	private List<MemoryManagerListener> listeners = new ArrayList<MemoryManagerListener>();

	public boolean add(MemoryManagerListener e) {
		return listeners.add(e);
	}

	public MemoryManager(Memory memory) {
		this.memory = memory;
	}

	public void manage(BasysProcess basysProcess) {
		boolean allocated = allocate(basysProcess);
		if (!allocated) {
			basysProcessQueue.add(basysProcess);
		}
	}

	/**
	 * @param basysProcess
	 *            Processo a ser alocado na memória
	 * @return <code>true</code> se o processo foi alocado, <code>false</code>
	 *         caso contrário
	 */
	private boolean allocate(BasysProcess basysProcess) {
		List<MemoryPartition> partitions = memory.getPartitions();
		boolean allocated = false;
		Integer newPartionSize = basysProcess.getSize();
		MemoryPartition oldPartition = findPartitionFor(newPartionSize);
		if (oldPartition != null) {
			MemoryPartition newPartition = new MemoryPartition(basysProcess,
					oldPartition.getStartBlock());
			oldPartition.setStartBlock(oldPartition.getStartBlock()
					+ newPartionSize);
			partitions.add(newPartition);
			Collections.sort(partitions);
			allocated = true;
			for (MemoryManagerListener listener : listeners) {
				listener.onProccesIsAllocated(basysProcess);
			}
		}
		return allocated;
	}

	private MemoryPartition findPartitionFor(Integer size) {
		MemoryPartition result = null;
		List<MemoryPartition> partitions = memory.getPartitions();
		for (MemoryPartition partition : partitions) {
			if (partition.isEmpty() && partition.getSize() >= size) {
				result = partition;
			}
		}
		return result;
	}

	public void deallocate(BasysProcess basysProcess) {
		if (basysProcess == null)
			throw new IllegalArgumentException();
		List<MemoryPartition> partitions = memory.getPartitions();
		boolean deallocated = false;
		for (MemoryPartition partition : partitions) {
			if (basysProcess.equals(partition.getProcess())) {
				partition.setEmpty();
				reorganizePartitions();
				deallocated = true;
				for (MemoryManagerListener listener : listeners) {
					listener.onProccesIsDeallocated(basysProcess);
				}
				processQueue();
				break;
			}
		}
		if (!deallocated) {
			throw new IllegalArgumentException("Nao foi possivel desalocar "+basysProcess);
		}
	}

	private void processQueue() {
		if (!basysProcessQueue.isEmpty()) {
			BasysProcess basysProcess = basysProcessQueue.peek();
			boolean allocated = allocate(basysProcess);
			if (allocated) {
				basysProcessQueue.remove(basysProcess);
				processQueue();
			}
		}
	}

	private void reorganizePartitions() {
		List<MemoryPartition> partitions = memory.getPartitions();
		boolean hasChanged = true;
		while (hasChanged) {
			hasChanged = false;
			for (int i = 0; i < partitions.size() - 1; i++) {
				if (partitions.get(i).isEmpty()
						&& partitions.get(i + 1).isEmpty()) {
					partitions.get(i).setEndBlock(
							partitions.get(i + 1).getEndBlock());
					partitions.remove(i + 1);
					Collections.sort(partitions);
					hasChanged = true;
					break;
				}
			}
		}
	}
}
